package simmar.xcs;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import simmar.utilities.IO;
import simmar.utilities.Logger;
import simmar.xcs.XCSConfig.MODE;

public class XCS implements XCSConfig
{
    protected Map<String, XCSClassifier> _population;
    protected List<XCSClassifier> _matchSet;
    protected float[] _predictionArray;
    protected List<XCSClassifier> _previousActionSet;
    protected List<XCSClassifier> _currentActionSet;
    protected int[] _currentCondition;
    protected int[] _previousCondition;
    protected float _currentReward;
    protected float _previousReward;
    protected int _iterations;

    public XCS()
    {
        _matchSet = null;
        _predictionArray = null;
        _currentActionSet = null;
        _previousActionSet = null;
        _currentCondition = null;
        _previousCondition = null;
        _currentReward = 0;
        _previousReward = 0;
        _iterations = -1; // Start with -1 because the loop starts with the reinforcement component
        
        if (XCSConfig.XCS_MODE == MODE.LEARN)
            _population = new HashMap<String, XCSClassifier>(CLASSIFIER_POPULATION_SIZE);            
        else if (XCSConfig.XCS_MODE == MODE.RUN || XCSConfig.XCS_MODE == MODE.TEST)
        {
            Logger.logLine("Loading population");
            _population = IO.LoadObject("population");
            printClassifiers();
        }
        else
            Logger.logLine("Invalid XCS MODE");
    }
    
//    public Action getAction(int[] condition)
//    {
//        Action action = null;
//        Iterator<Entry<String, XCSClassifier>> it = _population.entrySet().iterator();
//
//        while (it.hasNext())
//        {
//            XCSClassifier nextClassifier = it.next().getValue();
//
//            if (XCSPerformance.conditionMatch(condition, nextClassifier.getRule().getCondition()))
//            {
////                Logger.logLine("Classifier " + nextClassifier.getRule().toString() + " added to match set");
//                action = nextClassifier.getRule().getAction();
//            }
//        }
//        
//        return action;
//    }

    public Action runPerformanceComponent(int[] condition)
    {
//        System.out.println("---------------------------------------------------------------------------------------------------------");
//        Logger.log("**** XCS PERFORMANCE COMPONENT ****\n");
//        System.out.println("---------------------------------------------------------------------------------------------------------");

        
        if (XCS_DEBUG_LEVEL.compareTo(DEBUG_LEVEL.NORMAL) > 0)
        {
            Logger.logLine("Iteration " + _iterations);
            printClassifiers();
        }            
        
        _predictionArray = null;       
        _currentCondition = condition;
        List<XCSClassifier> matchSet = XCSPerformance.generateMatchSet(_iterations, _currentCondition, _population);
        _predictionArray = XCSPerformance.generatePredictionArray(matchSet);
        Action selectedAction = XCSPerformance.selectAction(_predictionArray);
        _currentActionSet = XCSPerformance.generateActionSet(selectedAction, matchSet);

         return selectedAction;
    }

    public void runReinforcementComponent(float reward, boolean endOfProblem)
    {
//        System.out.println("---------------------------------------------------------------------------------------------------------");
//        Logger.log("**** XCS REINFORCEMENT COMPONENT ****\n");
//        System.out.println("---------------------------------------------------------------------------------------------------------");

        // Return if it's the first time called, because main loop starts here
        if (_currentActionSet == null)
        {
            _iterations++; // From -1 to 0
            return;
        }          
        
        _currentReward = reward;
        
        if (XCS_DEBUG_LEVEL.compareTo(DEBUG_LEVEL.NORMAL) > 0)
            Logger.logLine("Received reward: " + _currentReward);

        if (_previousActionSet != null)
        {
            // Find max prediction (duplicated from generateActionSet, TODO: move to function)
            float bestPrediction = Float.NEGATIVE_INFINITY;

            for (int i = 0; i < MARIO_ACTIONS.length; i++)
                if (!Float.isNaN(_predictionArray[i]) && _predictionArray[i] > bestPrediction)
                    bestPrediction = _predictionArray[i];
            
            float P = _previousReward + DISCOUNT_FACTOR * bestPrediction;
            XCSReinforcement.updateActionSet(P, _previousActionSet, _population);
            XCSDiscovery.runGAComponent(_iterations, _previousCondition, _previousActionSet, _population);
        }

        if (endOfProblem)
        {
            float P = _currentReward;
            XCSReinforcement.updateActionSet(P, _currentActionSet, _population);
            XCSDiscovery.runGAComponent(_iterations, _currentCondition, _currentActionSet, _population);
            _previousActionSet = null;
        } else
        {
            _previousActionSet = _currentActionSet;
            _previousReward = _currentReward;
            _previousCondition = _currentCondition;
        }

        _iterations++;        
    }
    
    public int getPopulationSize()
    {
        return _population.size();
    }
    
    public int getPopulationSizeReal()
    {
        return XCSEdit.getPopulationSize(_population);
    }
    
    public void savePopulation()
    {
        Logger.logLine("Saving population");
        IO.SaveObject(_population, "population");
    }
    
    protected void printRules()
    {
        Logger.logLine("Population is:\n");
        
        Iterator<Entry<String,XCSClassifier>> iterator = _population.entrySet().iterator();
        
        while (iterator.hasNext())
        {
            Entry<String,XCSClassifier> nextEntry = iterator.next();
//            Logger.logLine("Key:\n" + nextEntry.getKey());
            Logger.logLine("Value:\n" + nextEntry.getValue().getRule().toString());
        }            
    }
    
    protected void printClassifiers()
    {
        Logger.logLine("Population is:\n");
        
        Iterator<Entry<String,XCSClassifier>> iterator = _population.entrySet().iterator();
        
        while (iterator.hasNext())
        {
            Entry<String,XCSClassifier> nextEntry = iterator.next();
//            Logger.logLine("Key:\n" + nextEntry.getKey());
//            Logger.logLine("Value:\n" + nextEntry.getValue().toString());
            Logger.log(nextEntry.getValue().toString() + "\n\n");
        }            
    }
}